﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using APNS.NET;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;

namespace APNSUnitTests.given_an_apnsnnotificationchannel
{
   
    public class when_I_send_a_notification
    {

        protected MockRepository mocks;
        protected Queue<IApnsConnection> connections;
        protected ApnsConnectionPool connectionPool;

        public virtual void Setup()
        {
            mocks = new MockRepository();
            connections = new Queue<IApnsConnection>();
            connectionPool = new ApnsConnectionPool(connections.Dequeue);
        }

        [TestClass]
        public class and_there_were_no_errors : when_I_send_a_notification
        {

            [TestInitialize]
            public override void Setup()
            {
                base.Setup();
            }

            [TestMethod]
            public void the_notification_will_be_transmitted()
            {
                
                IApnsConnection connection = mocks.Stub<IApnsConnection>();
                connection.Stub(c => c.Connected).Return(true);
                connection.Stub(c => c.TransmitNotification(Arg<ApnsNotification>.Is.Anything)).Return(null);
                connections.Enqueue(connection);

                mocks.ReplayAll();

                using (ApnsNotificationChannel channel = new ApnsNotificationChannel(connectionPool, 1, 3))
                {
                    ApnsNotification notification = new ApnsNotification();
                    channel.SendNotification(notification);
                    
                    var results = channel.Close();
                    var result = results[notification].First();

                    Assert.IsTrue(result.Success);
                    Assert.AreEqual(NotificationStatusCode.NoErrorsEncountered, result.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.None, result.FailReason);                    
                }
            }

            [TestMethod]
            public void all_notifications_will_be_transmitted()
            {
                List<NotificationWithHistory> notifications = new List<NotificationWithHistory>();
                int numThreads = 10;
                for(int i=0;i<10;i++)
                {
                    IApnsConnection connection = mocks.Stub<IApnsConnection>();
                    connections.Enqueue(connection);
                    connection.Stub(c => c.TransmitNotification(Arg<ApnsNotification>.Is.Anything)).Return(null);
                    connection.Stub(c => c.Connected).Return(true);
                }

               
                for(int i=0;i<100;i++)
                {
                    ApnsNotification notification = new ApnsNotification();
                    NotificationWithHistory notificationWithHistory = new NotificationWithHistory(notification);                    
                    notifications.Add(notificationWithHistory);
                }

                mocks.ReplayAll();
                using (ApnsNotificationChannel channel = new ApnsNotificationChannel(connectionPool, numThreads, 3))
                {

                    foreach (var apnNotification in notifications)
                    {
                        channel.SendNotification(apnNotification);
                    }
                    
                    var results = channel.Close();

                    foreach (var apnNotification in notifications)
                    {
                        var result = results[apnNotification.InternalNotification].First();

                        Assert.IsTrue(result.Success);
                        Assert.AreEqual(NotificationStatusCode.NoErrorsEncountered, result.StatusCode);
                        Assert.AreEqual(ApnsFailureReason.None, result.FailReason);
                    }
                    
                }
            }


        }

        [TestClass]
        public class and_there_were_message_errors : when_I_send_a_notification
        {

            [TestInitialize]
            public override void Setup()
            {
                base.Setup();
            }

            [TestMethod]
            public void the_notification_will_fail()
            {

                ApnsNotification notification = new ApnsNotification();
                NotificationWithHistory notificationWithHistory = new NotificationWithHistory(notification);

                IApnsConnection connection1 = mocks.Stub<IApnsConnection>();
                connection1.Stub(c => c.Connected).Return(false);
                connection1.Stub(c => c.TransmitNotification(notificationWithHistory)).Return( new ApnsNotificationResponse(1, NotificationStatusCode.InvalidTokenSize));
                connection1.Stub(c => c.GetTransmittedNotificationWithId(1)).Return(notificationWithHistory);
                connection1.Stub(c => c.GetTransmittedNotifications(1)).Return(new List<ApnsNotification>());
                connection1.Stub(c => c.Close());
                connections.Enqueue(connection1);

                mocks.ReplayAll();

                using (ApnsNotificationChannel channel = new ApnsNotificationChannel(connectionPool, 1, 3))
                {

                    channel.SendNotification(notificationWithHistory);

                    var results = channel.Close();                    
                    var result1 = results[notification].First();

                    Assert.IsFalse(result1.Success);
                    Assert.AreEqual(NotificationStatusCode.InvalidTokenSize, result1.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.ProcessingError, result1.FailReason);

                }
            }

            [TestMethod]
            public void subsequent_notifications_will_be_resent()
            {

                ApnsNotification notification1 = new ApnsNotification();
                NotificationWithHistory notificationWithHistory1 = new NotificationWithHistory(notification1);
                ApnsNotification notification2 = new ApnsNotification();
                NotificationWithHistory notificationWithHistory2 = new NotificationWithHistory(notification2);

                IApnsConnection connection1 = mocks.Stub<IApnsConnection>();
                connection1.Stub(c => c.Connected).Return(true);
                connection1.Stub(c => c.TransmitNotification(notificationWithHistory1)).Return(null);
                connection1.Stub(c => c.TransmitNotification(notificationWithHistory2)).Return(new ApnsNotificationResponse(1, NotificationStatusCode.InvalidTokenSize));                
                connection1.Stub(c => c.GetTransmittedNotificationWithId(1)).Return(notificationWithHistory1);
                connection1.Stub(c => c.GetTransmittedNotifications(1)).Return(new List<ApnsNotification> { notificationWithHistory2 });                
                connection1.Stub(c => c.Close());
                connections.Enqueue(connection1);

                IApnsConnection connection2 = mocks.Stub<IApnsConnection>();
                connection2.Stub(c => c.Connected).Return(true);
                connection2.Stub(c => c.TransmitNotification(notificationWithHistory2)).Return(null);
                connection2.Stub(c => c.Close());
                connections.Enqueue(connection2);

                mocks.ReplayAll();

                using (ApnsNotificationChannel channel = new ApnsNotificationChannel(connectionPool, 1, 3))
                {
                    
                    channel.SendNotification(notificationWithHistory1);
                    channel.SendNotification(notificationWithHistory2);

                    var results = channel.Close();

                    var result1 = results[notification1].First();
                    var result2 = results[notification2].First();
                    var result3 = results[notification2].Last();

                    Assert.IsFalse(result1.Success);
                    Assert.AreEqual(NotificationStatusCode.InvalidTokenSize, result1.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.ProcessingError, result1.FailReason);

                    Assert.IsFalse(result2.Success);
                    Assert.AreEqual(NotificationStatusCode.NoneUnknown, result2.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.PreviousNotificationFailed, result2.FailReason);

                    Assert.IsTrue(result3.Success);
                    Assert.AreEqual(NotificationStatusCode.NoErrorsEncountered, result3.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.None, result3.FailReason);
                    

                }
            }

        }

        [TestClass]
        public class and_there_were_processing_errors : when_I_send_a_notification
        {

            [TestInitialize]
            public override void Setup()
            {
                base.Setup();
            }

            [TestMethod]
            public void the_notification_will_be_retried_until_success()
            {

                ApnsNotification notification = new ApnsNotification();
                NotificationWithHistory notificationWithHistory = new NotificationWithHistory(notification);

                IApnsConnection connection1 = mocks.Stub<IApnsConnection>();
                connection1.Stub(c => c.Connected).Return(true);
                connection1.Stub(c => c.TransmitNotification(notificationWithHistory)).Return(new ApnsNotificationResponse(1, NotificationStatusCode.ProcessingError));
                connection1.Stub(c => c.GetTransmittedNotificationWithId(1)).Return(notificationWithHistory);
                connection1.Stub(c => c.GetTransmittedNotifications(1)).Return(new List<ApnsNotification>());
                connection1.Stub(c => c.Close());
                connections.Enqueue(connection1);

                IApnsConnection connection2 = mocks.Stub<IApnsConnection>();
                connection2.Stub(c => c.Connected).Return(true);
                connection2.Stub(c => c.TransmitNotification(notificationWithHistory)).Return(null);
                connection2.Stub(c => c.Close());
                connections.Enqueue(connection2);

                mocks.ReplayAll();

                using (ApnsNotificationChannel channel = new ApnsNotificationChannel(connectionPool, 1, 3))
                {
                    channel.SendNotification(notificationWithHistory);

                    var results = channel.Close();
                    var result1 = results[notification].First();
                    var result2 = results[notification].Last();

                    Assert.IsFalse(result1.Success);
                    Assert.AreEqual(NotificationStatusCode.ProcessingError, result1.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.ProcessingError, result1.FailReason);

                    Assert.IsTrue(result2.Success);
                    Assert.AreEqual(NotificationStatusCode.NoErrorsEncountered, result2.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.None, result2.FailReason);
                }
            }

            [TestMethod]
            public void subsequent_notifications_will_be_resent()
            {

                ApnsNotification notification1 = new ApnsNotification();
                NotificationWithHistory notificationWithHistory1 = new NotificationWithHistory(notification1);
                ApnsNotification notification2 = new ApnsNotification();
                NotificationWithHistory notificationWithHistory2 = new NotificationWithHistory(notification2);

                IApnsConnection connection1 = mocks.Stub<IApnsConnection>();
                connection1.Stub(c => c.Connected).Return(true);
                connection1.Stub(c => c.TransmitNotification(notificationWithHistory1)).Return(null);
                connection1.Stub(c => c.TransmitNotification(notificationWithHistory2)).Return(new ApnsNotificationResponse(1, NotificationStatusCode.ProcessingError));
                connection1.Stub(c => c.GetTransmittedNotificationWithId(1)).Return(notificationWithHistory1);
                connection1.Stub(c => c.GetTransmittedNotifications(1)).Return(new List<ApnsNotification> { notificationWithHistory2 });
                connection1.Stub(c => c.Close());
                connections.Enqueue(connection1);

                IApnsConnection connection2 = mocks.Stub<IApnsConnection>();
                connection2.Stub(c => c.Connected).Return(true);
                connection2.Stub(c => c.TransmitNotification(notificationWithHistory1)).Return(null);
                connection2.Stub(c => c.TransmitNotification(notificationWithHistory2)).Return(null);
                connection2.Stub(c => c.Close());
                connections.Enqueue(connection2);

                mocks.ReplayAll();

                using (ApnsNotificationChannel channel = new ApnsNotificationChannel(connectionPool, 1, 3))
                {

                    channel.SendNotification(notificationWithHistory1);
                    channel.SendNotification(notificationWithHistory2);

                    var results = channel.Close();

                    var result1 = results[notification1].First();
                    var result2 = results[notification1].Last();

                    var result3 = results[notification2].First();
                    var result4 = results[notification2].Last();

                    Assert.IsFalse(result1.Success);
                    Assert.AreEqual(NotificationStatusCode.ProcessingError, result1.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.ProcessingError, result1.FailReason);

                    Assert.IsTrue(result2.Success);
                    Assert.AreEqual(NotificationStatusCode.NoErrorsEncountered, result2.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.None, result2.FailReason);

                    Assert.IsFalse(result3.Success);
                    Assert.AreEqual(NotificationStatusCode.NoneUnknown, result3.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.PreviousNotificationFailed, result3.FailReason);

                    Assert.IsTrue(result4.Success);
                    Assert.AreEqual(NotificationStatusCode.NoErrorsEncountered, result4.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.None, result4.FailReason);                   

                }
            }


        }

        [TestClass]
        public class and_the_connection_was_dropped : when_I_send_a_notification
        {

            [TestInitialize]
            public override void Setup()
            {
                base.Setup();
            }

            [TestMethod]
            public void the_notification_will_be_retried_until_success()
            {

                ApnsNotification notification = new ApnsNotification();
                NotificationWithHistory notificationWithHistory = new NotificationWithHistory(notification);

                IApnsConnection connection1 = mocks.Stub<IApnsConnection>();
                connection1.Stub(c => c.Connected).Return(false);
                connection1.Stub(c => c.TransmitNotification(notificationWithHistory)).Return(null);
                connection1.Stub(c => c.GetTransmittedNotificationWithId(1)).Return(notificationWithHistory);
                connection1.Stub(c => c.Close());
                connections.Enqueue(connection1);

                IApnsConnection connection2 = mocks.Stub<IApnsConnection>();
                connection2.Stub(c => c.Connected).Return(true);
                connection2.Stub(c => c.TransmitNotification(notificationWithHistory)).Return(null);
                connection2.Stub(c => c.Close());
                connections.Enqueue(connection2);

                mocks.ReplayAll();

                using (ApnsNotificationChannel channel = new ApnsNotificationChannel(connectionPool, 1, 3))
                {
                    channel.SendNotification(notificationWithHistory);

                    var results = channel.Close();
                    var result1 = results[notification].First();
                    var result2 = results[notification].Last();

                    Assert.IsFalse(result1.Success);
                    Assert.AreEqual(NotificationStatusCode.NoneUnknown, result1.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.ConnectionDropped, result1.FailReason);

                    Assert.IsTrue(result2.Success);
                    Assert.AreEqual(NotificationStatusCode.NoErrorsEncountered, result2.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.None, result2.FailReason);
                }
            }

            [TestMethod]
            public void the_notification_will_be_retried_until_max_retries()
            {

                ApnsNotification notification = new ApnsNotification();
                NotificationWithHistory notificationWithHistory = new NotificationWithHistory(notification);

                IApnsConnection connection1 = mocks.Stub<IApnsConnection>();
                connection1.Stub(c => c.Connected).Return(false);
                connection1.Stub(c => c.TransmitNotification(notificationWithHistory)).Return(null);
                connection1.Stub(c => c.GetTransmittedNotificationWithId(1)).Return(notificationWithHistory);
                connection1.Stub(c => c.Close());
                connections.Enqueue(connection1);

                IApnsConnection connection2 = mocks.Stub<IApnsConnection>();
                connection2.Stub(c => c.Connected).Return(false);
                connection2.Stub(c => c.TransmitNotification(notificationWithHistory)).Return(null);
                connection2.Stub(c => c.GetTransmittedNotificationWithId(1)).Return(notificationWithHistory);
                connection2.Stub(c => c.Close());
                connections.Enqueue(connection2);

                IApnsConnection connection3 = mocks.Stub<IApnsConnection>();
                connection3.Stub(c => c.Connected).Return(false);
                connection3.Stub(c => c.TransmitNotification(notificationWithHistory)).Return(null);
                connection3.Stub(c => c.GetTransmittedNotificationWithId(1)).Return(notificationWithHistory);
                connection3.Stub(c => c.Close());
                connections.Enqueue(connection3);

                mocks.ReplayAll();

                using (ApnsNotificationChannel channel = new ApnsNotificationChannel(connectionPool, 1, 3))
                {
                    channel.SendNotification(notificationWithHistory);

                    var results = channel.Close()[notification].ToList();
                    var result1 = results[0];
                    var result2 = results[1];
                    var result3 = results[2];

                    Assert.IsFalse(result1.Success);
                    Assert.AreEqual(NotificationStatusCode.NoneUnknown, result1.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.ConnectionDropped, result1.FailReason);

                    Assert.IsFalse(result2.Success);
                    Assert.AreEqual(NotificationStatusCode.NoneUnknown, result2.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.ConnectionDropped, result2.FailReason);

                    Assert.IsFalse(result3.Success);
                    Assert.AreEqual(NotificationStatusCode.NoneUnknown, result3.StatusCode);
                    Assert.AreEqual(ApnsFailureReason.ConnectionDropped, result3.FailReason);
                }
            }

            
        }

    }
}
